iT邦幫忙

2022 iThome 鐵人賽

DAY 17
0
Modern Web

開始搞懂React生態系系列 第 17

Day 17 初探狀態管理 - Flux 架構 與 useReducer

  • 分享至 

  • xImage
  •  

為什麼要做狀態管理

在 React 設定 state,會透過 useState 的第二個 setState Function 做處理,但這樣做會造成以下的問題。

  • 使用 setState Function 時,可以指定任值給 state
  • 當 state 是複雜結構的物件,但需要修改的只是部分的值,或是要修改的值在散佈在物件各階層,如果沒有清楚的標示,後續維護上很容易出錯。

舉例來看以下這個 state 結構

const [obj, setObj] = useState({
  a: '1',
  b: {
    c: '2'
  },
});

想修改 obj 中 a 或 c 的值,直覺上來看,會寫出以下的寫法

<button onClick={() => setObj({a: '01'})} /> Set a</button>
// 上面這樣的寫法是不對的
// 預期結果是 {a: '01', b: {c: '2'}}
// 但會變成是 {a: '01'} ❌ 
<button onClick={() => setObj({b: {c: '02'}})} /> Set c</button>
// 上面這樣的寫法是不對的
// 預期結果是 {a: '1', b: {c: '02'}}
// 但會變成是 {b: {c: '02'}} ❌ 

正確的寫法應為

<button onClick={() => setObj({...obj, a: '01'})} /> Set a</button>
<button onClick={() => setObj({...obj,b: {c: '02'}})} /> Set c</button>

當 state 物件越複雜,操作的方法越多,那 setState Function 的處理邏輯也就更難清楚的做管控。

Flux 架構

Facebook 的開發者為了解決複雜的資料狀態結構,而提出了 Flux 結構。在 Flux 觀念下,操作流程和資料的過程會變成如下:

  1. 會先定義有哪些「規則」Action 可以使用
  2. 透過 Dispatch,派發指定的規則 Action 和需要的參數 Payload 到 Store,Store 就是做集中做狀態管理的地方。
  3. Store 收到 Dispatch 依據 Action 操作 State,View 因為 State 變動而重新渲染。
  4. 依不同 Action 操作 State 的 Function,就是 Reducer Function。

後面介紹的常用的狀態管理工具 - Redux 就是採用 Flux 結構。

Store 會是集中管理專案各種所需的 State Data 及 Reducer Function 的地方,通常我們會獨立成一個模組,讓元件在需要時引入。

但現在要先介紹一個 React 提供用來實現簡易 Flux 結構的 Hook - useReducer。當狀態還只有在單一元件中的多樣化邏輯操作時,不需要為其特別建立 Store,只需要定義 Reducer Function 就可以了。

useReducer

使用時機

當我們發現操作 state 變得較為複雜,使用 useState 的 setState Function 做對應的狀態邏輯操作會散落在不同地方時,就可將 state 的改變統一放在 Reducer Function 去做管理。

如果元件的狀態簡單,用 useState 即可。若狀態變得複雜,就可以轉用 useReducer - 將操作狀態的邏輯從元件中的不同操作 Function 移到一個單獨的 Reducer Function 中去,使元件更易於管理。

語法

const [state, dispatch] = useReducer(reducerFn, initStateValue);

// 如果程式不需要重置 state,使用上述語法即可

or 

const [state, dispatch] = useReducer(reducerFn, initStateValue, initStateFn);

// initStateFn 讓你可以將初始化 state 的邏輯提取到 reducer 外。
// 而且也方便了將來處理重置 state 的 action

範例比較

使用 useState 製作的 Counter 元件

因為 state 是比較複雜的物件,所以可以發現在 setState Function 的處理上,是多樣化的操作方法。

const Counter = () => {
  // 初始化 state
  const initialState = { status: false, count: 0 };
  const [state, setState] = React.useState(initialState);
  return (
    <div>
      <div>Count: {state.count}</div>
      <div>Status: {String(state.status)}</div>
      <button onClick={()=>{
          setState({...state, count: state.count + 1})}
      }>Increment</button>&nbsp;
      <button onClick={()=>{
          setState({...state, count: state.count - 1})}
      }>Decrement</button>&nbsp;
      <button onClick={()=>{
          setState({...state, status: !state.status})}
      }>Change Status</button>&nbsp;      
      <button onClick={()=>{
          setState(initialState)}
      }>Reset</button>
    </div>
  );
};

操作結果:https://codepen.io/lala-lee-jobs/pen/RwygRMa?editors=0011

使用 useReducer 製作的 Counter 元件

const Counter = () => {
  // 初始化 state
  const initialState = { status: false, count: 0 };
  
  // 定義 reducer function,集中管控 state 及 action 對應的邏輯處理
  // 依據 action type 對應不同的邏輯處理及回傳要變動的 state 結果
  const reducer = (state, action) => {
    switch (action.type) {
      case "increment":
        return { ...state, count: state.count + 1 };
      case "decrement":
        return { ...state, count: state.count - 1 };
      case "reset":
        return {...initialState};
      case "change-status":
        return  { ...state, status: !state.status };
      default:
        return state;
    }
  }
  
  const [state, dispatch] = React.useReducer(reducer, initialState);
  
  return (
    <div>
      <div>Count: {state.count}</div>
      <div>Status: {String(state.status)}</div>
      <button onClick={
          () => dispatch({ type: "increment" })
      }>Increment</button>&nbsp;
      <button onClick={
          () => dispatch({ type: "decrement" })
      }>Decrement</button>&nbsp;
      <button onClick={
          () => dispatch({ type: "change-status" })
      }>Change Status</button>&nbsp;      
      <button onClick={
          () => dispatch({ type: "reset" })
      }>Reset</button>
    </div>
  );
};

操作結果:https://codepen.io/lala-lee-jobs/pen/rNvwMgL?editors=0011

使用 useReducer 的優點

  • dispatch 不會隨著 Render 而重新分配記憶體位置,在作為 props 傳入到子元件時不需要為其加上 useCallback
  • 更動 state 的邏輯被封裝在 reducer function 內,reducer 對應各種操作,返回對應的狀態,而不是將邏輯分散到元件各處。

Next

到目前為此,都是透過 props 讓父元件傳遞資料給子元件,但元件階層如果跨越了很多層級時,就會需要不斷的做 props 轉傳,接下來要介紹的 useContext Hook 就可以做到在多層元件間的資料管理與傳遞。

Reference

https://hackmd.io/@Heidi-Liu/redux

https://chentsulin.github.io/redux/index.html

https://blog.csdn.net/ftell/article/details/121411371


上一篇
Day 16 React.memo (HOC)
下一篇
Day 18 多層級元件間的狀態管理 - Context & useContext
系列文
開始搞懂React生態系30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言